﻿using MongoDB.Bson;
using MongoDB.Driver;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.Entities;
using Sustainalytics.Entities.ConnectionStrings;
using Sustainalytics.Entities.ProductInvolvement;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Synchronization.Interface;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;

namespace Sustainalytics.ClientDW.Synchronization.SyncProductInvolvement
{
    public class PITraditionalSyncStep : ISyncStep
    {
        private SyncContext _syncContext;
        private ILogger _logger;

        public SyncStepOrder ExecutionOrder => SyncStepOrder.PITraditional;

        private readonly TimeSpan _hBaseWaitRetryDuration = TimeSpan.FromSeconds(int.Parse(ConfigurationManager.AppSettings["HBaseWaitRetryDuration"]));
        private readonly int _hBaseRetryNumberOfTimes = int.Parse(ConfigurationManager.AppSettings["HBaseRetryNumberOfTimes"]);

        public SyncContext Start(SyncContext syncContext)
        {
            _syncContext = syncContext;
            _logger = new SyncContextLogger(syncContext);
            try
            {
                _syncContext.Token.ThrowIfCancellationRequested();
                _syncContext.Log("Begining PITraditional data synchronization");

                SyncPITraditionalData();

                _syncContext.Token.ThrowIfCancellationRequested();
                _syncContext.Log("Ending PITraditional data synchronization");

                _syncContext.Status = SyncOperationStatus.Succeeded;
            }
            catch (AggregateException ex)
            {
                _syncContext.Log(ex.Flatten().ToString());
                _syncContext.Status = SyncOperationStatus.Failed;
                throw;
            }
            catch (Exception ex)
            {
                _syncContext.Log(ex.ToString());
                _syncContext.Status = SyncOperationStatus.Failed;
                throw;
            }

            return _syncContext;
        }

        private void SyncPITraditionalData()
        {
            var (companiesInProgress, assesmentList, indicatorList) = ReadData();

            SaveData(assesmentList, indicatorList);

            UpdateResearchStates(companiesInProgress, assesmentList);
        }

        private (List<Guid> CompaniesInProgress, List<ProductInvolvementData> AssesmentList, List<ProductInvolvementFramework> IndicatorList) ReadData()
        {
            var sw = Stopwatch.StartNew();

            var cddbData = Retry.Do(() => PICddb.GetInstance(SharedEntities.Interfaces.Enums.ResearchFramework.ProductInvolvement)
                           .GetPICompanies(), _hBaseWaitRetryDuration, _logger, _hBaseRetryNumberOfTimes);

            sw.Stop();
            _syncContext.Log($"{cddbData.Count} CDDB entities retrieved. Elapsed time: {sw.Elapsed}");

            sw.Restart();

            var storedProcedure = "researchedCompaniesSnapshot";
            var storedProcedureCompanyList = "researchedCompanyList_Traditional";
            var indicatorsStoredProcedure = "getProductInvolvementIndicatorData";

            var data = Retry.Do(() => PISqlRepository.GetInstance(cddbData, storedProcedure, storedProcedureCompanyList, indicatorsStoredProcedure, null)
                       .GetTraditionalData(), _hBaseWaitRetryDuration, _logger, _hBaseRetryNumberOfTimes);

            sw.Stop();
            _syncContext.Log($"{data.AssesmentList.Count + data.IndicatorList.Count} PITraditional data entities retrieved from SQL. Elapsed time: {sw.Elapsed}");

            return data;
        }

        private void SaveData(List<ProductInvolvementData> assesmentList, List<ProductInvolvementFramework> indicatorList)
        {
            var sw = Stopwatch.StartNew();

            var indicatorsCollection = GetCollection<ProductInvolvementFramework>(ConnectionStringType.ProductInvolvementDW.ToString());

            indicatorsCollection.Database.DropCollection(typeof(ProductInvolvementFramework).Name);

            indicatorsCollection.InsertMany(indicatorList);

            var rowsAffected = indicatorsCollection.Count(new BsonDocument());

            sw.Stop();
            _syncContext.Log($"{rowsAffected} PIIndicator entities saved. Elapsed time: {sw.Elapsed}");

            sw.Restart();
            var assesmentCollection = GetCollection<ProductInvolvementData>(ConnectionStringType.ProductInvolvementDW.ToString());

            assesmentCollection.Database.DropCollection(typeof(ProductInvolvementData).Name);

            assesmentCollection.InsertMany(assesmentList);

            rowsAffected = assesmentCollection.Count(new BsonDocument());

            sw.Stop();
            _syncContext.Log($"{rowsAffected} PICompanyAssessment entities saved. Elapsed time: {sw.Elapsed}");
        }

        private void UpdateResearchStates(List<Guid> companiesInProgress, List<ProductInvolvementData> assesmentList)
        {
            var sw = Stopwatch.StartNew();

            var corporateDataCollection = GetCollection<CorporateData>(ConnectionStringType.ClientDW.ToString());

            var companyProfileResearchStateCollection = GetCollection<CompanyProfileResearchState>(ConnectionStringType.ClientDW.ToString());

            var count = 0;
            foreach (var companyId in companiesInProgress)
            {
                if (assesmentList.Any(x => x.Id == companyId))
                    continue;

                var corporateFilter = Builders<CorporateData>.Filter.Eq(x => x.Id, companyId)
                    & Builders<CorporateData>.Filter.Eq(x => x.ProductInvolvementResearchState.ResearchState, ResearchState.ResearchAvailable);

                if (corporateDataCollection.Count(corporateFilter) != 1)
                    continue;

                var corporateUpdate = Builders<CorporateData>.Update.Set(x => x.ProductInvolvementResearchState.ResearchState, ResearchState.ResearchInProgress)
                                                                    .Set(x => x.ProductInvolvementResearchState.HasResearch, false);
                corporateDataCollection.UpdateOne(corporateFilter, corporateUpdate);

                var profileFilter = Builders<CompanyProfileResearchState>.Filter.Eq(x => x.Id, companyId);
                var profileUpdate = Builders<CompanyProfileResearchState>.Update.Set(x => x.ProductInvolvementResearchState.ResearchState, ResearchState.ResearchInProgress)
                                                                                .Set(x => x.ProductInvolvementResearchState.HasResearch, false);
                companyProfileResearchStateCollection.UpdateOne(profileFilter, profileUpdate);

                _syncContext.Log($"ResearchState set to ResearchInProgress for company {companyId.ToString()}");
                count++;
            }

            sw.Stop();
            _syncContext.Log($"{count} companies found with no SQL data have been set to ResearchInProgress. Elapsed time: {sw.Elapsed}");
        }


        private IMongoCollection<T> GetCollection<T>(string database) where T : IEntity
        {
            var connectionString = String.Empty;
           
            if (!_syncContext.GAConnectionStrings.TryGetValue(database, out connectionString))
                throw new ArgumentException($"No connection string found for {database}!");

            return MongoFactory.GetCollectionFromDatabase<T>(connectionString);
        }
    }
}
